package org.testfire.realm.control.user;


import com.lookingglasssoftware.lgsrealm.LGSChecksum;
import org.apache.log4j.Logger;
import org.testfire.realm.control.NavBean;
import org.testfire.realm.model.entities.User;
import org.testfire.realm.model.entities.User_groups;
import org.testfire.realm.util.Mailer;
import org.testfire.realm.util.UtilityBean;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import java.util.HashSet;
import java.util.Set;

/**
 * A bean for 'Registering' a new m_user; methods for verifying that a m_user name is unique, ensuring
 * valid password and m_user name, and sending out out a confirmation e-mail so that the
 * m_user may confirm their account.
 *
 * @author Geoff M. Granum
 * @version 1.0
 */
public class RegisterUserBean extends UserBean {

    /**
     * A valid password.
     */
    private String password = "";

    /**
     * Confirm that the m_user entered the same password twice.
     */
    private String password2 = "";

    private final Logger logger = Logger.getLogger(this.getClass());


    public RegisterUserBean() {
        super();
    }

    /**
     * Use the UserDBAO to create a new managed instance of the User Entity.
     *
     * @return A new, managed instance of the User Entity class.
     */
    public User getNewUser() {
        try {
            this.m_user = dbao().getNewUser();
        } catch (Exception e) {
            logger.error(e);
            m_user = null;
        }
        return m_user;
    }

    /**
     * Registers a new m_user.
     *
     * @return String
     * @throws Exception On validation error
     */
    public String createNewUser() throws Exception {
        logger.info("RegisterUserBean.createNewUser");
        boolean success;
        if (getUser() == null // fail on these conditions.
                || this.password == null
                || this.password2 == null
                || !this.password.equals(password2)
                || !isValidPassword(password)) {
            logger.warn("error creating new m_user");
            success = false;
        } else {
            try {
                // create the checksum via the HMAC of the password as a key and the
                // concatonation of the username and the system salt for the data block.
                String md5 = LGSChecksum.getHMAC_MD5(this.password, getUser().getName() + UtilityBean.SYSTEM_SALT);
                getUser().setMd5(md5); // set it on the m_user.
                User_groups g = new User_groups(); //
                g.setUser_id(getUser());
                g.setGroup_id(1);
                Set<User_groups> set = new HashSet<User_groups>();
                set.add(g);
                getUser().setUser_groups(set);
                this.password = null;
                this.password2 = null;
                // registering means that we can't check the request for the userName
                // It also means we need to set a confirm code:
                getUser().setConfirm_code(createConfirmCode());
                super.updateUserEntity(false);
                success = true;
                // If we fail, it should be BEFORE mailing the m_user.
                mailRegistrationConfim();
            }
            catch (Exception ex) {
                logger.error("", ex);
                throw ex;
            }
        }
        return success ? "nav_register_success" : "nav_register_fail";
    }

    /**
     * We aren't very anal about passwords, as they will be turned into MD5 checksums.
     *
     * @param pwd String
     * @return boolean
     */
    public static boolean isValidPassword(String pwd) {
        return (pwd != null && pwd.length() >= 6 && pwd.length() <= 30);
    }

    /**
     * Use the Mailer class to send a confirmation e-mail to the registering m_user.
     */
    private void mailRegistrationConfim() {
        try {
            Mailer.sendMessage(getUser().getEmail(), getMailMessage().toString());
        }
        catch (Exception e) {
            logger.error("", e);
        }
    }

    /**
     * Create the registration link.
     *
     * @return The new registration link as a string.
     */
    private String getRegistrationConfirmLink() {
        return new NavBean().getHref_register_confirm() + "?user=" + getUser().getName() + "&confirmCode=" + getUser().getConfirm_code();
    }


    /**
     * Create the body of our mail message; this should be a resource property or at least extracted to a
     * more logical location.
     *
     * @return The message body for the confirmation e-mail.
     */
    private StringBuilder getMailMessage() {
        StringBuilder b = new StringBuilder();
        b.append("Welcome to TestFire!\n")
                .append("We will need you to confirm your registration ")
                .append("because, you know, there are a few thousand people out there ")
                .append("that make life miserable for the rest of us.\n\n")
                .append("If you did not register on getTestFire.org, please ignore this message.")
                .append(" And... we're sorry to bother you. You won't receive anything more from us, we promise!\n\n\n")
                .append("If you DID register with us, please click on the link that follows to complete the ")
                .append("registration process.\n\n")
                .append(getRegistrationConfirmLink())
                .append("\n\n\nThanks Again!\n\n\n--\nThe getTestFire team.");


        return b;
    }

    /**
     * The registration page requires this to fail if the m_user DOES exist; so it does.
     * Which is backwards from the other method version (userExists(String)).
     *
     * @param context    FacesContext
     * @param toValidate UIComponent
     * @param value      Object
     */
    public void userExists(FacesContext context, UIComponent toValidate, Object value) {
        logger.info("Validating User via context");
        String msg = "Invalid UserName or Password.";
        boolean failed = dbao().userExists((String) value); // fail if the m_user DOES exist.
        if (failed) {
            ((UIInput) toValidate).setValid(false);
            context.addMessage(toValidate.getClientId(context), new FacesMessage(msg));
        }
    }

    /**
     * Validates the users password; assumes that the username has already been set.
     *
     * @param context    FacesContext
     * @param toValidate UIComponent
     * @param value      Object
     */
    public void isValidPassword(FacesContext context, UIComponent toValidate, Object value) {
        logger.info("RegisterUserBean.isValidPassword");
        String msg = "Password must be between 6 and 20 characters long.";
        String pwd = (String) value;
        try {
            if (!isValidPassword(pwd)) {
                ((UIInput) toValidate).setValid(false);
                context.addMessage(toValidate.getClientId(context), new FacesMessage(msg));
            }
        }
        catch (Exception ex) {
            logger.error("", ex);
            ((UIInput) toValidate).setValid(false);
            context.addMessage(toValidate.getClientId(context), new FacesMessage(msg));
        }
    }

    /**
     * Get the password for the m_user being registered.
     *
     * @return Usually returns an empty string, as the get* calls are processed mostly
     *         before any values have actually been set.
     */
    public String getPassword() {
        return password;
    }

    /**
     * Gets the value entered as the second 'confirm password' value.
     * Confirm that the m_user entered the same password twice.
     *
     * @return The value entered as the second 'confirm password' value.
     */
    public String getPassword2() {
        return password2;
    }


    /**
     * Set the password for the m_user being registered.
     * Because we need to have a real password value in order to create
     * the m_user, the registration should be done via SSL. (the site template does this).
     *
     * @param password the password for the m_user being registered.
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Set the 'confirmation password' value. Ensures that the m_user did not make a typo in their
     * choice of password.
     *
     * @param password2 the 'confirmation password' value
     */
    public void setPassword2(String password2) {
        this.password2 = password2;
    }


}
